Отформатировать списки параметров по код-стайлу

Больше двух параметров — каждый на отдельной строке (закрывающая
скобка в конце последнего); два и меньше — в одну строку. Правило
добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам
в 253 файлах.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:22:56 +03:00
parent 410194b0cb
commit 492950bdd0
255 changed files with 2672 additions and 668 deletions
@@ -72,7 +72,10 @@ public interface ICardStore
/// <param name="space">Пространство переставляемых контейнеров.</param>
/// <param name="containerIds">Id контейнеров в новом порядке.</param>
/// <param name="ct">Токен отмены.</param>
public Task ReorderContainersAsync(string space, IReadOnlyList<string> containerIds, CancellationToken ct);
public Task ReorderContainersAsync(
string space,
IReadOnlyList<string> containerIds,
CancellationToken ct);
// ── Карточки ──────────────────────────────────────────────────────────
@@ -100,7 +103,10 @@ public interface ICardStore
/// <param name="ct">Токен отмены.</param>
/// <returns>Полные карточки (комментарии приложены, time посчитан): FTS-кандидаты по
/// убыванию ts_rank (SearchTsv @@ plainto_tsquery), затем LIKE-дополнение, внутри — ReceivedAt DESC.</returns>
public Task<IReadOnlyList<CardDto>> SearchCardsAsync(string q, int limit, CancellationToken ct);
public Task<IReadOnlyList<CardDto>> SearchCardsAsync(
string q,
int limit,
CancellationToken ct);
/// <summary>
/// Одна карточка по id (GET /api/cards/{id}, а также перечитывание после переноса).
@@ -119,7 +125,10 @@ public interface ICardStore
/// <param name="msgId">Id исходного сообщения в Telegram.</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Карточка или null, если сообщение не становилось карточкой.</returns>
public Task<CardDto?> GetCardBySourceAsync(string dialogId, long msgId, CancellationToken ct);
public Task<CardDto?> GetCardBySourceAsync(
string dialogId,
long msgId,
CancellationToken ct);
/// <summary>
/// Создаёт карточку из готового снимка (пайплайн этапа 4, ручное создание; CreatedAt — UTC-now).
@@ -135,7 +144,10 @@ public interface ICardStore
/// <param name="patch">Изменяемые поля (null — поле не меняется; JSON-поля — полная замена).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>True — строка обновлена; false — карточки нет (404-семантика сервиса).</returns>
public Task<bool> PatchCardAsync(string cardId, CardPatch patch, CancellationToken ct);
public Task<bool> PatchCardAsync(
string cardId,
CardPatch patch,
CancellationToken ct);
/// <summary>
/// Атомарно дописывает ссылку в JSON-массив links ОДНИМ UPDATE (jsonb-append) + bump UpdatedAt.
@@ -144,7 +156,10 @@ public interface ICardStore
/// <param name="link">Готовая ссылка {id,name,url} (id сгенерирован модулем).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>True — строка обновлена; false — карточки нет (404-семантика сервиса).</returns>
public Task<bool> AddLinkAsync(string cardId, CardLinkDto link, CancellationToken ct);
public Task<bool> AddLinkAsync(
string cardId,
CardLinkDto link,
CancellationToken ct);
/// <summary>
/// Атомарно убирает из JSON-массива links элемент с указанным id ОДНИМ UPDATE (jsonb-фильтрация) + bump UpdatedAt.
@@ -153,7 +168,10 @@ public interface ICardStore
/// <param name="linkId">Id удаляемой ссылки (<c>pl_...</c>).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>True — строка обновлена; false — карточки нет (404-семантика сервиса).</returns>
public Task<bool> RemoveLinkAsync(string cardId, string linkId, CancellationToken ct);
public Task<bool> RemoveLinkAsync(
string cardId,
string linkId,
CancellationToken ct);
/// <summary>
/// Атомарно дописывает метаданные файла в JSON-массив files ОДНИМ UPDATE (jsonb-append) + bump UpdatedAt.
@@ -162,7 +180,10 @@ public interface ICardStore
/// <param name="file">Готовые метаданные {id,name,size,kind,label,objectKey} (id сгенерирован модулем).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>True — строка обновлена; false — карточки нет (404-семантика сервиса).</returns>
public Task<bool> AddFileAsync(string cardId, CardFileDto file, CancellationToken ct);
public Task<bool> AddFileAsync(
string cardId,
CardFileDto file,
CancellationToken ct);
/// <summary>
/// Атомарно убирает из JSON-массива files элемент с указанным id ОДНИМ UPDATE (jsonb-фильтрация) + bump UpdatedAt.
@@ -171,7 +192,10 @@ public interface ICardStore
/// <param name="fileId">Id удаляемой записи файла (<c>pf_...</c>).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>True — строка обновлена; false — карточки нет (404-семантика сервиса).</returns>
public Task<bool> RemoveFileAsync(string cardId, string fileId, CancellationToken ct);
public Task<bool> RemoveFileAsync(
string cardId,
string fileId,
CancellationToken ct);
/// <summary>
/// Смена контейнера-стадии: один UPDATE (col, reminder_at=NULL, reminder_fired=false, updated_at=atMs)
@@ -183,7 +207,12 @@ public interface ICardStore
/// <param name="atMs">Время переноса, epoch-ms (пишется в updated_at и в запись истории).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>True — строка обновлена; false — карточки нет (404-семантика сервиса).</returns>
public Task<bool> MoveCardStageAsync(string cardId, string containerId, CardHistoryDto historyEntry, long atMs, CancellationToken ct);
public Task<bool> MoveCardStageAsync(
string cardId,
string containerId,
CardHistoryDto historyEntry,
long atMs,
CancellationToken ct);
/// <summary>
/// Устанавливает напоминание: reminder_at, reminder_fired=false + bump UpdatedAt (set_reminder projects.py L236–243).
@@ -191,7 +220,10 @@ public interface ICardStore
/// <param name="cardId">Id карточки (<c>c_...</c>).</param>
/// <param name="atMs">Время напоминания, epoch-ms.</param>
/// <param name="ct">Токен отмены.</param>
public Task SetReminderAsync(string cardId, long atMs, CancellationToken ct);
public Task SetReminderAsync(
string cardId,
long atMs,
CancellationToken ct);
/// <summary>
/// Снимает напоминание: reminder_at=NULL, reminder_fired=false (clear_reminder projects.py L246–247).
@@ -253,7 +285,10 @@ public interface ICardStore
/// <param name="cardId">Id карточки либо null.</param>
/// <param name="col">Колонка либо null.</param>
/// <param name="ct">Токен отмены.</param>
public Task UpdateSeenAsync(string? cardId, string? col, CancellationToken ct);
public Task UpdateSeenAsync(
string? cardId,
string? col,
CancellationToken ct);
/// <summary>
/// Удаляет карточку навсегда: Cards + комментарии (FK cascade) + строки дедупа карточки
@@ -297,7 +332,12 @@ public interface ICardStore
/// <param name="by">Автор («Вы» — свои комментарии).</param>
/// <param name="text">Текст (непустой — валидирует сервис, 400 «Пустой комментарий»).</param>
/// <param name="ct">Токен отмены.</param>
public Task AddCommentAsync(string commentId, string cardId, string by, string text, CancellationToken ct);
public Task AddCommentAsync(
string commentId,
string cardId,
string by,
string text,
CancellationToken ct);
// ── Журнал действий (CardMoves = learning_log) ────────────────────────
@@ -348,7 +388,10 @@ public interface ICardStore
/// <param name="archivedAt">Момент архивации (один «now» тика).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Сколько карточек реально архивировано (0 — кандидатов не было/уже не в рабочих колонках).</returns>
public Task<int> ArchiveAsync(IReadOnlyList<string> cardIds, DateTimeOffset archivedAt, CancellationToken ct);
public Task<int> ArchiveAsync(
IReadOnlyList<string> cardIds,
DateTimeOffset archivedAt,
CancellationToken ct);
/// <summary>
/// Кандидаты на очистку архива: col='archive' и ArchivedAt старше срока (tick_storage L475–478).
@@ -392,7 +435,12 @@ public interface ICardStore
/// <param name="convTo">Сконвертированная верхняя граница, либо null.</param>
/// <param name="convCur">Валюта конверсии (целевая валюта тенанта); пусто — конверсия снята.</param>
/// <param name="ct">Токен отмены.</param>
public Task UpdateConversionAsync(string cardId, double? convFrom, double? convTo, string convCur, CancellationToken ct);
public Task UpdateConversionAsync(
string cardId,
double? convFrom,
double? convTo,
string convCur,
CancellationToken ct);
// ── Эвристика ИИ-предложений (Ruling 3, Task 14) ───────────────────────
@@ -41,7 +41,12 @@ public interface IMlLearningStore
/// <param name="label">Метка обучения: id доски, <c>spam</c> либо <c>t:hire|t:order</c>.</param>
/// <param name="delta">Вес сигнала (1.0 — учить, −1.0 — снять метку).</param>
/// <param name="ct">Токен отмены.</param>
public Task AddOutboxAsync(string id, string text, string label, double delta, CancellationToken ct);
public Task AddOutboxAsync(
string id,
string text,
string label,
double delta,
CancellationToken ct);
/// <summary>
/// Полная очистка очереди обучения: DELETE FROM MlOutbox (ml_client.reset_model L122). Журнал CardMoves не трогается.
@@ -68,7 +68,10 @@ public static class AmountParser
var occupied = new List<(int Start, int End)>();
// Свободен ли диапазон (не пересекается с уже занятым конструкцией выше) — прототип _free L106–107.
static bool IsFree(List<(int Start, int End)> spans, int start, int end)
static bool IsFree(
List<(int Start, int End)> spans,
int start,
int end)
{
foreach ((int os, int oe) in spans)
{
@@ -81,7 +84,12 @@ public static class AmountParser
return true;
}
void Add(double? from, double? to, string? cur, int start, int end)
void Add(
double? from,
double? to,
string? cur,
int start,
int end)
{
if (cur is not null && (from is not null || to is not null) && IsFree(occupied, start, end))
{
@@ -28,7 +28,10 @@ public static class BudgetInRange
/// True — есть сумма, которая после конвертации (при необходимости) ≥ from и ≤ to; обе границы null → True;
/// пустые amounts или непереводимая валюта → False.
/// </returns>
public static bool IsInRange(IReadOnlyList<AmountRange> amounts, BudgetRangeDto budget, IReadOnlyDictionary<string, double>? rates)
public static bool IsInRange(
IReadOnlyList<AmountRange> amounts,
BudgetRangeDto budget,
IReadOnlyDictionary<string, double>? rates)
{
double? lo = budget.From;
double? hi = budget.To;
@@ -24,7 +24,10 @@ public static class ColumnMatcher
/// <param name="rates">Курсы для конвертации бюджета (см. <see cref="BudgetInRange"/>); null — если бюджет в
/// другой валюте, суммы не конвертируются и группа бюджета не совпадает.</param>
/// <returns>True — текст прошёл правила в режиме all/any.</returns>
public static bool MatchText(ContainerRulesDto? rules, string? text, IReadOnlyDictionary<string, double>? rates = null)
public static bool MatchText(
ContainerRulesDto? rules,
string? text,
IReadOnlyDictionary<string, double>? rates = null)
{
if (rules is null)
{
@@ -28,7 +28,10 @@ public static class ColumnRules
/// False — сработало слово-исключение (veto) либо текст не прошёл активные правила;
/// True — правил нет/неактивны или текст прошёл правила.
/// </returns>
public static bool ContainerAccepts(ContainerRulesDto? rules, string? text, IReadOnlyDictionary<string, double>? rates = null)
public static bool ContainerAccepts(
ContainerRulesDto? rules,
string? text,
IReadOnlyDictionary<string, double>? rates = null)
{
if (ColumnExclusions.IsExcluded(rules, text))
{
@@ -50,7 +53,10 @@ public static class ColumnRules
/// <param name="text">Текст сообщения.</param>
/// <param name="rates">Курсы для конвертации бюджета.</param>
/// <returns>True — текст прошёл правила в режиме all/any (исключения не учитываются).</returns>
public static bool Matches(ContainerRulesDto? rules, string? text, IReadOnlyDictionary<string, double>? rates = null)
public static bool Matches(
ContainerRulesDto? rules,
string? text,
IReadOnlyDictionary<string, double>? rates = null)
{
return ColumnMatcher.MatchText(rules, text, rates);
}
@@ -75,7 +81,10 @@ public static class ColumnRules
/// Список совпавших критериев (MatchHitDto label/term/word); для пустых/null правил и колонок без
/// активных правил — [] (Ruling 2). Исключения (veto) в список не входят (прототип hits L271–296).
/// </returns>
public static IReadOnlyList<MatchHitDto> ComputeHits(ContainerRulesDto? rules, string? text, IReadOnlyDictionary<string, double>? rates = null)
public static IReadOnlyList<MatchHitDto> ComputeHits(
ContainerRulesDto? rules,
string? text,
IReadOnlyDictionary<string, double>? rates = null)
{
if (!ColumnMatcher.HasActiveRules(rules))
{
@@ -50,7 +50,10 @@ public static class MatchHitBuilder
/// <param name="rates">Курсы для конвертации бюджета (см. <see cref="BudgetInRange"/>).</param>
/// <returns>Список совпадений по группам; порядок — direction → keywords → stack → grade → levels →
/// locations → types → budget → prices.</returns>
public static IReadOnlyList<MatchHitDto> BuildHits(ContainerRulesDto? rules, string? text, IReadOnlyDictionary<string, double>? rates = null)
public static IReadOnlyList<MatchHitDto> BuildHits(
ContainerRulesDto? rules,
string? text,
IReadOnlyDictionary<string, double>? rates = null)
{
var result = new List<MatchHitDto>();
if (rules is null)
@@ -144,7 +147,10 @@ public static class MatchHitBuilder
// label: Метка группы.
// lower: Текст в нижнем регистре.
// Возвращает: Hits по каждому совпавшему терму (term сохраняет регистр пользователя).
private static IEnumerable<MatchHitDto> MatchedTermHits(IReadOnlyList<string>? rawTerms, string label, string lower)
private static IEnumerable<MatchHitDto> MatchedTermHits(
IReadOnlyList<string>? rawTerms,
string label,
string lower)
{
if (rawTerms is null)
{
@@ -99,7 +99,10 @@ public static class RulesDescriber
// parts: Накопитель частей описания.
// range: Диапазон группы (null — группа выключена).
// title: Подпись группы («бюджет»/«цена»).
private static void AddRangePart(List<string> parts, BudgetRangeDto? range, string title)
private static void AddRangePart(
List<string> parts,
BudgetRangeDto? range,
string title)
{
if (range is null || (range.From is null && range.To is null))
{
@@ -174,7 +174,11 @@ public static class BudgetNormalizer
// toCurrency: Целевая валюта (код).
// rates: Курсы к рублю либо null.
// Возвращает: Сумма в целевой валюте или null.
private static double? Convert(double amount, string fromCurrency, string toCurrency, IReadOnlyDictionary<string, double>? rates)
private static double? Convert(
double amount,
string fromCurrency,
string toCurrency,
IReadOnlyDictionary<string, double>? rates)
{
return rates is null ? null : RatesService.ConvertAmount(amount, fromCurrency, toCurrency, rates);
}
@@ -101,7 +101,10 @@ public sealed partial class CardsService
/// <param name="fileId">Id записи файла (<c>pf_...</c>).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Метаданные записи файла либо null (карточка/запись не найдены).</returns>
public async Task<CardFileDto?> GetFileEntryAsync(string cardId, string fileId, CancellationToken ct)
public async Task<CardFileDto?> GetFileEntryAsync(
string cardId,
string fileId,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
@@ -124,7 +127,10 @@ public sealed partial class CardsService
/// <param name="fileId">Id удаляемой записи файла (<c>pf_...</c>).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Карточка после удаления (без записи) либо null — карточки нет (404-семантика).</returns>
public async Task<CardDto?> RemoveFileAsync(string cardId, string fileId, CancellationToken ct)
public async Task<CardDto?> RemoveFileAsync(
string cardId,
string fileId,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
@@ -152,7 +158,10 @@ public sealed partial class CardsService
// fileId: Id записи файла (pf_...; уникальный суффикс ключа).
// name: Имя файла как прислано (в ключ идёт санитизированная часть).
// Возвращает: Ключ объекта (opaque для хранилища).
private static string BuildObjectKey(string cardId, string fileId, string name)
private static string BuildObjectKey(
string cardId,
string fileId,
string name)
{
return $"{ObjectRootSegment}/{cardId}/{fileId}_{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{SanitizeKeyName(name)}";
}
@@ -42,7 +42,12 @@ public sealed partial class CardsService
// hits: matchHits для новой колонки (пересчитаны вызывающим, Ruling 2).
// action: Действие журнала: move/trash.
// ct: Токен отмены.
private async Task MoveToColumnAsync(CardDto card, string toCol, IReadOnlyList<MatchHitDto> hits, string action, CancellationToken ct)
private async Task MoveToColumnAsync(
CardDto card,
string toCol,
IReadOnlyList<MatchHitDto> hits,
string action,
CancellationToken ct)
{
await _store.UpdateColumnAsync(new CardColumnUpdateDto(
CardId: card.Id,
@@ -60,7 +65,12 @@ public sealed partial class CardsService
// fromCol: Прежняя колонка (для comment — null).
// toCol: Новая колонка (для comment — null).
// ct: Токен отмены.
private async Task LogMoveAsync(string cardId, string action, string? fromCol, string? toCol, CancellationToken ct)
private async Task LogMoveAsync(
string cardId,
string action,
string? fromCol,
string? toCol,
CancellationToken ct)
{
await _store.AddMoveAsync(new CardMoveDto(
PrefixId.New(KanbanIdPrefixes.CardMove),
@@ -75,7 +85,10 @@ public sealed partial class CardsService
// text: Текст карточки для правил (source_msg или title).
// ct: Токен отмены.
// Возвращает: Список совпавших критериев; доски нет/правил нет → пусто.
private async Task<IReadOnlyList<MatchHitDto>> ComputeHitsForBoardAsync(string boardId, string text, CancellationToken ct)
private async Task<IReadOnlyList<MatchHitDto>> ComputeHitsForBoardAsync(
string boardId,
string text,
CancellationToken ct)
{
ContainerDto? board = await _store.GetContainerAsync(boardId, ct);
return board is null
@@ -89,7 +102,10 @@ public sealed partial class CardsService
// text: Текст карточки для правил.
// ct: Токен отмены.
// Возвращает: Совпавшие критерии (label/term[/word]); нет активных правил → пусто.
private async Task<IReadOnlyList<MatchHitDto>> ComputeHitsAsync(ContainerRulesDto? rules, string text, CancellationToken ct)
private async Task<IReadOnlyList<MatchHitDto>> ComputeHitsAsync(
ContainerRulesDto? rules,
string text,
CancellationToken ct)
{
// Кэш курсов из типизированного снимка настроек (C30): null → мок-курсы (дефолт RatesService).
IReadOnlyDictionary<string, double> rates =
@@ -66,7 +66,10 @@ public sealed partial class CardsService
/// <param name="toCol">Цель: <c>inbox</c> либо id доски (<c>b_...</c>).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Результат: Error (400) | Card=null (карточки нет, 404) | Card — карточка после переноса.</returns>
public async Task<CardResultDto> MoveDashboardCardAsync(string cardId, string toCol, CancellationToken ct)
public async Task<CardResultDto> MoveDashboardCardAsync(
string cardId,
string toCol,
CancellationToken ct)
{
ContainerDto? board = null;
if (toCol != CardIds.Inbox)
@@ -138,7 +141,10 @@ public sealed partial class CardsService
/// <param name="teach">True — писать сигнал «спам» (действие пользователя); false — не писать.</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Карточка после переноса (при no-op — как была) либо null — карточки нет (404).</returns>
public async Task<CardDto?> TrashCardAsync(string cardId, bool teach, CancellationToken ct)
public async Task<CardDto?> TrashCardAsync(
string cardId,
bool teach,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
@@ -255,7 +261,10 @@ public sealed partial class CardsService
/// <param name="text">Текст комментария (непустой после Trim).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Результат: Error (400) | Comments=null (404) | Comments — список после добавления.</returns>
public async Task<AddCommentResultDto> AddCommentAsync(string cardId, string text, CancellationToken ct)
public async Task<AddCommentResultDto> AddCommentAsync(
string cardId,
string text,
CancellationToken ct)
{
string trimmed = (text ?? string.Empty).Trim();
if (trimmed.Length == 0)
@@ -284,7 +293,10 @@ public sealed partial class CardsService
/// <param name="cardId">Id карточки либо null/пусто.</param>
/// <param name="col">Колонка либо null/пусто (используется, когда cardId не задан).</param>
/// <param name="ct">Токен отмены.</param>
public Task MarkSeenAsync(string? cardId, string? col, CancellationToken ct)
public Task MarkSeenAsync(
string? cardId,
string? col,
CancellationToken ct)
{
return _store.UpdateSeenAsync(
string.IsNullOrEmpty(cardId) ? null : cardId,
@@ -48,7 +48,10 @@ public sealed partial class CardsService
/// <param name="ct">Токен отмены.</param>
/// <returns>Результат: Error <see cref="RemindersDisabledDetail"/> (400) | Card=null без Error (404) |
/// Card — карточка с напоминанием.</returns>
public async Task<CardResultDto> SetReminderAsync(string cardId, long atMs, CancellationToken ct)
public async Task<CardResultDto> SetReminderAsync(
string cardId,
long atMs,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
@@ -168,7 +168,10 @@ public sealed partial class CardsService
/// <param name="body">Тело PATCH: ключ → JSON-значение (наличие ключа = поле меняется).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Обновлённая карточка или null — карточки нет (404).</returns>
public async Task<CardDto?> PatchCardAsync(string cardId, IReadOnlyDictionary<string, JsonElement> body, CancellationToken ct)
public async Task<CardDto?> PatchCardAsync(
string cardId,
IReadOnlyDictionary<string, JsonElement> body,
CancellationToken ct)
{
ArgumentNullException.ThrowIfNull(body);
@@ -190,7 +193,11 @@ public sealed partial class CardsService
/// <param name="url">URL ссылки (без схемы — добавится https://).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Результат: Error (400 «Пустая ссылка») | Card=null без Error (404) | Card — карточка со ссылкой.</returns>
public async Task<CardResultDto> AddLinkAsync(string cardId, string name, string url, CancellationToken ct)
public async Task<CardResultDto> AddLinkAsync(
string cardId,
string name,
string url,
CancellationToken ct)
{
CardDto? card = await _store.GetCardAsync(cardId, ct);
if (card is null)
@@ -236,7 +243,10 @@ public sealed partial class CardsService
/// <param name="linkId">Id удаляемой ссылки (<c>pl_...</c>).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Результат: Card=null без Error (404) | Card — карточка без ссылки.</returns>
public async Task<CardResultDto> RemoveLinkAsync(string cardId, string linkId, CancellationToken ct)
public async Task<CardResultDto> RemoveLinkAsync(
string cardId,
string linkId,
CancellationToken ct)
{
if (!await _store.RemoveLinkAsync(cardId, linkId, ct))
{
@@ -262,7 +272,10 @@ public sealed partial class CardsService
/// <param name="ct">Токен отмены.</param>
/// <returns>Результат: Error «Неизвестная стадия» (400) | Card=null без Error (карточки нет, 404) |
/// Card — карточка после переноса.</returns>
public async Task<CardResultDto> MoveStageCardAsync(string cardId, string containerId, CancellationToken ct)
public async Task<CardResultDto> MoveStageCardAsync(
string cardId,
string containerId,
CancellationToken ct)
{
if (!CardsDefaultContainers.Contains(containerId))
{
@@ -52,7 +52,11 @@ public sealed partial class CardsService
/// <param name="settings">KV-хранилище настроек тенанта (курсы, напоминания).</param>
/// <param name="mlClient">Клиент ML: PushAsync — обучающий сигнал действия, StatusAsync — счётчики counts.</param>
/// <param name="storage">Файловое хранилище вложений карточки (объекты файлов).</param>
public CardsService(ICardStore store, ISettingsStore settings, IMlClient mlClient, IFileStorage storage)
public CardsService(
ICardStore store,
ISettingsStore settings,
IMlClient mlClient,
IFileStorage storage)
{
_store = store;
_settings = settings;
@@ -122,7 +122,10 @@ public sealed class ContainersService(ICardStore store, ISettingsStore settings)
/// <param name="patch">Изменения; null-поле означает «не менять».</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Контейнер после патча; null — контейнера нет (404 «Контейнер не найден»).</returns>
public async Task<ContainerDto?> PatchAsync(string containerId, ContainerPatchDto patch, CancellationToken ct)
public async Task<ContainerDto?> PatchAsync(
string containerId,
ContainerPatchDto patch,
CancellationToken ct)
{
ContainerDto? current = await store.GetContainerAsync(containerId, ct);
if (current is null)
@@ -171,7 +174,10 @@ public sealed class ContainersService(ICardStore store, ISettingsStore settings)
/// <param name="space">Пространство переставляемых контейнеров.</param>
/// <param name="containerIds">Id контейнеров в новом порядке.</param>
/// <param name="ct">Токен отмены.</param>
public Task ReorderAsync(string space, IReadOnlyList<string> containerIds, CancellationToken ct)
public Task ReorderAsync(
string space,
IReadOnlyList<string> containerIds,
CancellationToken ct)
{
return store.ReorderContainersAsync(space, containerIds, ct);
}
@@ -195,7 +201,10 @@ public sealed class ContainersService(ICardStore store, ISettingsStore settings)
/// <param name="patch">Изменяемые поля состояния.</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Состояние колонки после merge.</returns>
public async Task<ColumnStateDto> PatchColStateAsync(string colId, ColumnStateDto patch, CancellationToken ct)
public async Task<ColumnStateDto> PatchColStateAsync(
string colId,
ColumnStateDto patch,
CancellationToken ct)
{
Dictionary<string, ColumnStateDto> state = await ReadColStateAsync(ct);
state.TryGetValue(colId, out ColumnStateDto? current);
@@ -121,9 +121,7 @@ public static class SuggestHeuristics
/// <param name="inbox">Карточки «Неразобранного» с непустым source_msg (ICardStore.ListInboxWithSourceAsync).</param>
/// <param name="existingBoardNames">Имена существующих (suggested=false) досок — похожие темы не предлагаются.</param>
/// <returns>Планы колонок (≤4, каждая ≥2 карточки); пусто — мало карточек/нечего сгруппировать.</returns>
public static IReadOnlyList<SuggestedColumnPlan> PlanColumns(
IReadOnlyList<CardDto> inbox,
IReadOnlyList<string> existingBoardNames)
public static IReadOnlyList<SuggestedColumnPlan> PlanColumns(IReadOnlyList<CardDto> inbox, IReadOnlyList<string> existingBoardNames)
{
// Окно анализа: свежие карточки с текстом, как выборка SQL L96–99 (сортировка — страховка
// детерминизма: адаптер уже отдаёт received_at DESC, но вход не должен влиять на выход).