Отформатировать списки параметров по код-стайлу
Больше двух параметров — каждый на отдельной строке (закрывающая скобка в конце последнего); два и меньше — в одну строку. Правило добавлено в docs/spec/Код-стайл-Дейл.md; применено к 628 сигнатурам в 253 файлах.
This commit is contained in:
@@ -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, но вход не должен влиять на выход).
|
||||
|
||||
Reference in New Issue
Block a user