Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Агрегат карточки: ядро + опциональные модули (единая сущность всех дашбордов).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Один класс на карточку (не иерархия видов): вид определяется контейнером и наполненностью модулей,
|
||||
/// а не типом. Модули — это секции данных (роли интерфейсов), пустые, пока карточка «входящая»,
|
||||
/// наполняются, когда карточка «в работе» (файлы/ссылки/ТЗ/история/напоминания). Класс неизменяемый
|
||||
/// (init/свойства только для чтения) — изменения применяются сервисами через порт и возвращают новый
|
||||
/// снимок, как единая CardDto (wire 1:1 сохраняется).
|
||||
/// </remarks>
|
||||
public sealed class Card :
|
||||
ICard,
|
||||
IContentCard,
|
||||
IBudgetedCard,
|
||||
IContactCard,
|
||||
IAttributedCard,
|
||||
ICommentableCard,
|
||||
ILinkCard,
|
||||
IFileCard,
|
||||
ITzCard,
|
||||
ITraceableCard,
|
||||
IRemindableCard,
|
||||
ILocatedCard
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISource Source { get; init; } = null!;
|
||||
|
||||
// ── Модуль «содержимое» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Summary { get; init; } = string.Empty;
|
||||
|
||||
// ── Модуль «бюджет» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public CardBudget? Budget { get; init; }
|
||||
|
||||
// ── Модуль «контакты» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CardContact> Contacts { get; init; } = Array.Empty<CardContact>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContactText { get; init; } = string.Empty;
|
||||
|
||||
// ── Модуль «атрибуты» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CardAttribute> Attributes { get; init; } = Array.Empty<CardAttribute>();
|
||||
|
||||
// ── Модуль «комментарии» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CardComment> Comments { get; init; } = Array.Empty<CardComment>();
|
||||
|
||||
// ── Модуль «ссылки» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CardLink> Links { get; init; } = Array.Empty<CardLink>();
|
||||
|
||||
// ── Модуль «файлы» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CardFile> Files { get; init; } = Array.Empty<CardFile>();
|
||||
|
||||
// ── Модуль «ТЗ» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public string TzText { get; init; } = string.Empty;
|
||||
|
||||
// ── Модуль «история движения» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<CardHistoryEntry> History { get; init; } = Array.Empty<CardHistoryEntry>();
|
||||
|
||||
// ── Модуль «напоминание» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public CardReminder? Reminder { get; init; }
|
||||
|
||||
// ── Модуль «размещение» ──
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ContainerId { get; init; } = CardIds.Inbox;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? PrevContainerId { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsNew { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Настраиваемый атрибут карточки: имя, значение и (опционально) единица измерения.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Стек/грейд/локация/сроки/площадь и прочее — не «зашитые» поля карточки, а атрибуты из справочника
|
||||
/// тенанта (пользователь настраивает в UI, «стек» — лишь частый атрибут). Один атрибут = одна запись
|
||||
/// «ключ → значение»; ключом служит строковый id из справочника атрибутов.
|
||||
/// </remarks>
|
||||
public sealed record CardAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Id атрибута (ключ справочника: <c>stack</c>, <c>grade</c>, <c>location</c>, …).
|
||||
/// </summary>
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Значение атрибута (например, «Python», «Middle», «Москва»).
|
||||
/// </summary>
|
||||
public string Value { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Единица измерения (₽/₽/час/м²/…); null — безразмерный.
|
||||
/// </summary>
|
||||
public string? Unit { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Бюджет/цена карточки в исходной валюте и (опционально) сконвертированный в целевую.
|
||||
/// </summary>
|
||||
public sealed record CardBudget
|
||||
{
|
||||
/// <summary>
|
||||
/// Нижняя граница; null — не указана.
|
||||
/// </summary>
|
||||
public double? From { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Верхняя граница; null — не указана.
|
||||
/// </summary>
|
||||
public double? To { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Валюта (код/символ из исходных данных); пусто — бюджет не задан.
|
||||
/// </summary>
|
||||
public string Cur { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Пересчитанный в целевую валюту тенанта бюджет; null — конверсия не сделана.
|
||||
/// </summary>
|
||||
public CardBudget? Converted { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Комментарий карточки.
|
||||
/// </summary>
|
||||
public sealed record CardComment
|
||||
{
|
||||
/// <summary>
|
||||
/// Короткий id комментария.
|
||||
/// </summary>
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Автор (имя/«Вы»).
|
||||
/// </summary>
|
||||
public string By { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Текст комментария.
|
||||
/// </summary>
|
||||
public string Text { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Время создания, epoch-ms.
|
||||
/// </summary>
|
||||
public long AtMs { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Контакт заказчика: квалифицированная запись (тип + значение).
|
||||
/// </summary>
|
||||
public sealed record CardContact
|
||||
{
|
||||
/// <summary>
|
||||
/// Тип контакта: tg/phone/email/linkedin/whatsapp/site/other.
|
||||
/// </summary>
|
||||
public string Type { get; init; } = "other";
|
||||
|
||||
/// <summary>
|
||||
/// Значение: @username, +7…, name@mail, url.
|
||||
/// </summary>
|
||||
public string Value { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Метаданные прикреплённого файла карточки (объект хранится в S3/MinIO или локальной папке).
|
||||
/// </summary>
|
||||
public sealed record CardFile
|
||||
{
|
||||
/// <summary>
|
||||
/// Короткий id записи файла.
|
||||
/// </summary>
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Имя файла.
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Размер в байтах.
|
||||
/// </summary>
|
||||
public long SizeBytes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип контента: image/video/audio/archive/document/other (определяет детектор).
|
||||
/// </summary>
|
||||
public string Kind { get; init; } = "other";
|
||||
|
||||
/// <summary>
|
||||
/// Ключ объекта в хранилище.
|
||||
/// </summary>
|
||||
public string ObjectKey { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Запись истории движения карточки.
|
||||
/// </summary>
|
||||
public sealed record CardHistoryEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Короткий id записи.
|
||||
/// </summary>
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Время события, epoch-ms.
|
||||
/// </summary>
|
||||
public long AtMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип события: <c>created</c>|<c>createdLocal</c>|id контейнера назначения (движение).
|
||||
/// </summary>
|
||||
public string Type { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Реестр коротких id модуля Cards и служебных контейнеров (единый владелец карточки).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Единый префикс карточек <c>c_</c> приходит на смену <c>l_</c> (канбан) и <c>pr_</c> (проекты) — при
|
||||
/// слиянии таблиц у карточки один id, не зависящий от пространства. Служебные контейнеры
|
||||
/// (<c>inbox</c>/<c>archive</c>/<c>trash</c>) — фиксированные зоны любого пространства-дашборда.
|
||||
/// Контейнеры-доски (<c>b_…</c>) создаёт пользователь/ИИ; контейнеры-стадии — предзаданный каталог
|
||||
/// (см. CardsDefaultContainers). Id генерирует модуль (PrefixId), хранилище получает готовые.
|
||||
/// </remarks>
|
||||
public static class CardIds
|
||||
{
|
||||
/// <summary>
|
||||
/// Префикс id карточки (единый для всех дашбордов).
|
||||
/// </summary>
|
||||
public const string CardPrefix = "c_";
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер «Неразобранное»: новые карточки пайплайна до раскладки по колонкам.
|
||||
/// </summary>
|
||||
public const string Inbox = "inbox";
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер «Архив»: карточки старше срока архивации (автоочистка по политике).
|
||||
/// </summary>
|
||||
public const string Archive = "archive";
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер «Корзина»: удалённые карточки (автоочистка по политике).
|
||||
/// </summary>
|
||||
public const string Trash = "trash";
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Прикреплённая ссылка карточки.
|
||||
/// </summary>
|
||||
public sealed record CardLink
|
||||
{
|
||||
/// <summary>
|
||||
/// Короткий id ссылки.
|
||||
/// </summary>
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Подпись ссылки (пусто — url как подпись).
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Адрес ссылки (http/https).
|
||||
/// </summary>
|
||||
public string Url { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Результат перехода карточки единым механизмом <see cref="ICardMover"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Error — 400-текст отказа (несуществующий контейнер/исходная колонка, запрет политики); Exists=false —
|
||||
/// карточки нет (404). При успехе оба поля пусты/true — снимок карточки эндпоинт перечитывает единым
|
||||
/// чтением (CardDto), чтобы наружу всегда уходила одна форма.
|
||||
/// </remarks>
|
||||
/// <param name="Error">Текст 400-ошибки либо null.</param>
|
||||
/// <param name="Exists">True — карточка найдена и переход выполнен (либо перенос был no-op).</param>
|
||||
public sealed record CardMoveResultDto(string? Error, bool Exists);
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Напоминание об «Отложено» (карточка в контейнере hold).
|
||||
/// </summary>
|
||||
public sealed record CardReminder
|
||||
{
|
||||
/// <summary>
|
||||
/// Время срабатывания, epoch-ms.
|
||||
/// </summary>
|
||||
public long AtMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Признак «напоминание выстрелило» (повторно не срабатывает до переноса/переустановки).
|
||||
/// </summary>
|
||||
public bool Fired { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Каталог контейнеров по умолчанию пространства «Выбранные» (стадии) и служебных зон.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Приходит на смену ProjectStages модуля Projects: стадии — это контейнеры kind=stage предзаданного
|
||||
/// каталога (терминальные — finished/rejected). Хранилище провижинит их тенанту как строки Containers;
|
||||
/// каталог — единственный источник имён/цветов/порядка (как ProjectStages.All). Служебные зоны
|
||||
/// (inbox/archive/trash) — фиксированные контейнеры CardIds. Доски (kind=board) каталогу не принадлежат:
|
||||
/// их создаёт пользователь/ИИ.
|
||||
/// </remarks>
|
||||
public static class CardsDefaultContainers
|
||||
{
|
||||
/// <summary>
|
||||
/// Id стадии «Запланировано» (цель перехода «взять в работу»).
|
||||
/// </summary>
|
||||
public const string Planned = "planned";
|
||||
|
||||
/// <summary>
|
||||
/// Id стадии «Отклик».
|
||||
/// </summary>
|
||||
public const string Reply = "reply";
|
||||
|
||||
/// <summary>
|
||||
/// Id стадии «Согласование».
|
||||
/// </summary>
|
||||
public const string Agree = "agree";
|
||||
|
||||
/// <summary>
|
||||
/// Id стадии «В работе».
|
||||
/// </summary>
|
||||
public const string Work = "work";
|
||||
|
||||
/// <summary>
|
||||
/// Id стадии «Проверка».
|
||||
/// </summary>
|
||||
public const string Review = "review";
|
||||
|
||||
/// <summary>
|
||||
/// Id стадии «Готово».
|
||||
/// </summary>
|
||||
public const string Ready = "ready";
|
||||
|
||||
/// <summary>
|
||||
/// Id стадии «Отложено» (напоминания сбрасываются переносом).
|
||||
/// </summary>
|
||||
public const string Hold = "hold";
|
||||
|
||||
/// <summary>
|
||||
/// Id терминальной стадии «Выполнено».
|
||||
/// </summary>
|
||||
public const string Finished = "finished";
|
||||
|
||||
/// <summary>
|
||||
/// Id терминальной стадии «Отклонено» (очистка вручную, без возврата).
|
||||
/// </summary>
|
||||
public const string Rejected = "rejected";
|
||||
|
||||
/// <summary>
|
||||
/// Все контейнеры по умолчанию (стадии пространства «Выбранные»), порядок канбана.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<DefaultContainer> All { get; } = new List<DefaultContainer>
|
||||
{
|
||||
new(Planned, "Запланировано", "#818cf8", 0, terminal: false, restore: false),
|
||||
new(Reply, "Отклик", "#38bdf8", 1, terminal: false, restore: false),
|
||||
new(Agree, "Согласование", "#a78bfa", 2, terminal: false, restore: false),
|
||||
new(Work, "В работе", "#fbbf24", 3, terminal: false, restore: false),
|
||||
new(Review, "Проверка", "#f97316", 4, terminal: false, restore: false),
|
||||
new(Ready, "Готово", "#4ade80", 5, terminal: false, restore: false),
|
||||
new(Hold, "Отложено", "#94a3b8", 6, terminal: false, restore: false),
|
||||
new(Finished, "Выполнено", "#2bd576", 7, terminal: true, restore: false),
|
||||
new(Rejected, "Отклонено", "#ff6b6b", 8, terminal: true, restore: false),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Id всех контейнеров-стадий «Выбранных» (для фильтра пространства в выборках хранилища).
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> Ids { get; } = All.Select(container => container.Id).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Проверка: контейнер с таким id есть в каталоге (валидация move/ручного создания).
|
||||
/// </summary>
|
||||
/// <param name="containerId">Проверяемый id.</param>
|
||||
/// <returns>True — контейнер известен каталогу.</returns>
|
||||
public static bool Contains(string containerId)
|
||||
{
|
||||
return All.Any(s => s.Id == containerId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Описание контейнера по умолчанию (запись каталога CardsDefaultContainers).
|
||||
/// </summary>
|
||||
public sealed record DefaultContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Создаёт запись каталога контейнеров по умолчанию.
|
||||
/// </summary>
|
||||
/// <param name="id">Короткий id контейнера (стадии).</param>
|
||||
/// <param name="name">Имя для отображения.</param>
|
||||
/// <param name="color">Цвет (hex).</param>
|
||||
/// <param name="order">Позиция в канбане.</param>
|
||||
/// <param name="terminal">Терминальная зона (только ручная очистка, без возврата).</param>
|
||||
/// <param name="restore">Возврат из контейнера разрешён.</param>
|
||||
public DefaultContainer(string id, string name, string color, int order, bool terminal, bool restore)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Color = color;
|
||||
Order = order;
|
||||
IsTerminal = terminal;
|
||||
CanRestore = restore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Короткий id контейнера.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя для отображения.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Цвет (hex).
|
||||
/// </summary>
|
||||
public string Color { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Позиция в канбане.
|
||||
/// </summary>
|
||||
public int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Терминальная зона.
|
||||
/// </summary>
|
||||
public bool IsTerminal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Возврат разрешён.
|
||||
/// </summary>
|
||||
public bool CanRestore { get; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «ИИ»: карточка создана/сгенерирована ИИ (агент, генератор, разборщик).
|
||||
/// </summary>
|
||||
/// <remarks>Может быть составной частью <see cref="ICompositeSource"/>: первоисточник данных + ИИ-обработка.</remarks>
|
||||
public interface IAiSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Id провайдера ИИ (deepseek/openai/ollama/…).
|
||||
/// </summary>
|
||||
public string ProviderId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Модель провайдера.
|
||||
/// </summary>
|
||||
public string Model { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id ИИ-агента (если источник — агент, ищущий/обрабатывающий данные); null — не агент.
|
||||
/// </summary>
|
||||
public string? AgentId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на промпт/шаблон (если применимо); null — нет.
|
||||
/// </summary>
|
||||
public string? PromptRef { get; }
|
||||
|
||||
/// <summary>
|
||||
/// API, через который работал агент (для составных сценариев); null — нет.
|
||||
/// </summary>
|
||||
public IApiSource? ViaApi { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «внешний API»: карточка пришла из интеграции по API.
|
||||
/// </summary>
|
||||
public interface IApiSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Ключ интеграции/провайдера (конфигурация подключения живёт в настройках тенанта).
|
||||
/// </summary>
|
||||
public string ProviderId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Адрес эндпоинта (или иной указатель в рамках провайдера); null — не применимо.
|
||||
/// </summary>
|
||||
public string? Endpoint { get; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «атрибуты»: настраиваемые пользователем характеристики (стек, грейд, локация, сроки…).
|
||||
/// </summary>
|
||||
/// <remarks>Справочник атрибутов живёт в настройках тенанта; карточка хранит только значения по ключам.</remarks>
|
||||
public interface IAttributedCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Значения атрибутов карточки (пусто — не заполнены).
|
||||
/// </summary>
|
||||
public IReadOnlyList<CardAttribute> Attributes { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «бюджет/цена»: деньги заявки (валюта исходная + сконвертированная).
|
||||
/// </summary>
|
||||
public interface IBudgetedCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Бюджет карточки; null — сумма не указана.
|
||||
/// </summary>
|
||||
public CardBudget? Budget { get; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Ядро карточки: единственное, что есть у любой карточки во всех дашбордах.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Никаких «полей заявки» в ядре: стек/бюджет/контакты и прочее — опциональные модули-роли
|
||||
/// (см. <c>IContentCard</c>, <c>IBudgetedCard</c> и др.), которые реализует агрегат карточки
|
||||
/// по мере наполнения. <see cref="Source"/> — откуда карточка пришла (см. иерархию <c>ISource</c>);
|
||||
/// у карточки, созданной пайплайном из сообщения канала, источник составной:
|
||||
/// <c>ICompositeSource { Origin: ITelegramSource, Pipeline: [IAiSource/IMl] }</c>.
|
||||
/// Типизированный доступ к конкретному источнику — дискриминация через иерархию ISource
|
||||
/// (паттерн-матчинг), отдельный generic-интерфейс не нужен.
|
||||
/// </remarks>
|
||||
public interface ICard
|
||||
{
|
||||
/// <summary>
|
||||
/// Короткий id карточки (единый префикс карточек, напр. <c>c_</c>).
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Заголовок карточки (очищенный).
|
||||
/// </summary>
|
||||
public string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Источник: откуда карточка пришла (полиморфный, не enum).
|
||||
/// </summary>
|
||||
public ISource Source { get; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Единый механизм перехода карточки между контейнерами.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Заменяет маршрутизацию перехода по эндпоинтам: одна точка знает и контейнеры-стадии пространства
|
||||
/// «Выбранные», и контейнеры дашборда (доски/служебные зоны). Валидацию «откуда → куда можно» выполняют
|
||||
/// политики контейнеров и «ворота» между пространствами (например, дашборд → «Выбранные» — только через
|
||||
/// действие «взять в работу»; «Выбранные» → дашборд — запрещено). Побочные эффекты (запись истории для
|
||||
/// стадий, сброс напоминания, журнал/обучение ML для дашборда) выполняет реализация перехода.
|
||||
/// </remarks>
|
||||
public interface ICardMover
|
||||
{
|
||||
/// <summary>
|
||||
/// Перемещает карточку в контейнер назначения с побочными эффектами перехода.
|
||||
/// </summary>
|
||||
/// <param name="cardId">Id карточки.</param>
|
||||
/// <param name="toContainerId">Id контейнера назначения (стадия «Выбранных» либо дашборд-контейнер).</param>
|
||||
/// <param name="ctx">Контекст перехода (инициатор, причина, обучение).</param>
|
||||
/// <param name="ct">Токен отмены.</param>
|
||||
/// <returns>Результат: Error (400-текст отказа) | Exists=false (карточки нет, 404) | успех (Exists=true).</returns>
|
||||
public Task<CardMoveResultDto> MoveAsync(
|
||||
string cardId, string toContainerId, TransitionContext ctx, CancellationToken ct);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «комментарии» карточки.
|
||||
/// </summary>
|
||||
public interface ICommentableCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Комментарии карточки (пусто — комментариев нет).
|
||||
/// </summary>
|
||||
public IReadOnlyList<CardComment> Comments { get; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Составной источник: цепочка «где взято → как обработано/доставлено».
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// У карточки из пайплайна источник составной: <see cref="Origin"/> — первоисточник контента
|
||||
/// (например, <c>ITelegramSource</c>), <see cref="Pipeline"/> — кто/что его обрабатывал
|
||||
/// (например, ИИ-классификатор). Позволяет строить «третьи» источники без правки ядра.
|
||||
/// </remarks>
|
||||
public interface ICompositeSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Первоисточник контента.
|
||||
/// </summary>
|
||||
public ISource Origin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Цепочка обработки (пусто — обработки не было).
|
||||
/// </summary>
|
||||
public IReadOnlyList<ISource> Pipeline { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «контакты заказчика».
|
||||
/// </summary>
|
||||
public interface IContactCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Квалифицированные контакты (пусто — контактов нет).
|
||||
/// </summary>
|
||||
public IReadOnlyList<CardContact> Contacts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Контактная строка «как в исходных данных» (fallback, если Contacts пуст).
|
||||
/// </summary>
|
||||
public string ContactText { get; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Контейнер карточки: колонка дашборда, стадия «Выбранных» или служебная зона.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Общая база колонок/стадий/зон: пользовательские колонки (kind=board, создаёт пользователь/ИИ),
|
||||
/// стадии (kind=stage, предзаданный каталог), «Неразобранное», архив, корзина, терминальные зоны.
|
||||
/// Поведение контейнера — через <see cref="Policy"/> (роль, не enum-свойства): правила попадания,
|
||||
/// допустимость возврата, автоочистка, терминальность. Пространство (дашборд/«Выбранные»/третий вид) —
|
||||
/// свойство SpaceId: карточке безразличен вид, у неё только ContainerId.
|
||||
/// </remarks>
|
||||
public interface IContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Короткий id контейнера (доски <c>b_…</c>, стадии <c>planned…</c>, служебные inbox/archive/trash).
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя для отображения («WPF», «В работе», «Архив»).
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Цвет контейнера (hex).
|
||||
/// </summary>
|
||||
public string Color { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Позиция в пространстве (порядок показа слева направо).
|
||||
/// </summary>
|
||||
public int Order { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id пространства (доски), которому принадлежит контейнер.
|
||||
/// </summary>
|
||||
public string SpaceId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Политика контейнера: что можно/нельзя и что происходит (роль).
|
||||
/// </summary>
|
||||
public IContainerPolicy Policy { get; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Политика контейнера: правила размещения карточек и жизненного цикла зоны.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Роль контейнера (не enum): пользовательская колонка-фильтр, стадия, «Неразобранное», архив/корзина,
|
||||
/// терминальная зона — каждая реализует политику по-своему. Политика отвечает на два вопроса:
|
||||
/// (1) можно ли карточке попасть сюда (правила фильтрации) и (2) что происходит с карточкой здесь
|
||||
/// (возврат, автоочистка, терминальность, напоминания).
|
||||
/// </remarks>
|
||||
public interface IContainerPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// Правила попадания карточки в контейнер (null — фильтра нет, карточки кладутся вручную/ИИ).
|
||||
/// </summary>
|
||||
public IContainerRules? Rules { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Можно ли вернуть карточку из контейнера на доску пространства (архив/корзина — да; терминальные — нет).
|
||||
/// </summary>
|
||||
public bool CanRestore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Терминальная зона: карточка завершила жизненный путь, ручная очистка без возврата.
|
||||
/// </summary>
|
||||
public bool IsTerminal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Автоочистка контейнера: срок хранения карточек (null — автоочистки нет).
|
||||
/// </summary>
|
||||
public TimeSpan? Retention { get; }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Правила попадания карточки в контейнер (набор опциональных фильтров).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Соответствует «правилам колонки» (§6.3): направление/стек/ключевые слова/грейд/уровень/цена/бюджет/
|
||||
/// локация/тип и отрицательные исключения. Все группы опциональны; режим All/Any решает, как группы
|
||||
/// сочетаются. Пустые правила — контейнер без фильтра (карточки кладутся вручную или ИИ/ML по контексту).
|
||||
/// Проверка — по содержимому карточки (атрибуты, бюджет, текст), чистая функция.
|
||||
/// </remarks>
|
||||
public interface IContainerRules
|
||||
{
|
||||
/// <summary>
|
||||
/// Режим сочетания групп: <c>all</c> — должны совпасть все включённые группы; <c>any</c> — хотя бы одна.
|
||||
/// </summary>
|
||||
public string Mode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ключевые слова/фразы (пусто — группа не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Keywords { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ключевые технологии/стек-атрибуты (пусто — не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Stack { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Направление/тема (пусто — не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Directions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Грейд/уровень (пусто — не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Grades { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Уровень (отдельная группа §6.3; пусто — не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Levels { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Локация (пусто — не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Locations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Тип заявки: вакансия/фриланс/объявление (пусто — не участвует).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Types { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Бюджетный диапазон (null — не участвует).
|
||||
/// </summary>
|
||||
public CardBudget? Budget { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Диапазон цены (отдельная группа §6.3; null — не участвует).
|
||||
/// </summary>
|
||||
public CardBudget? Prices { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Исключения: карточка не попадает, если в тексте/атрибутах есть хотя бы одно (veto).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Exclude { get; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «содержимое»: структурированный блок «О заявке» карточки.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Единая структура у всех карточек (Компания → Формат → О задаче → Требования → Будет плюсом → Условия);
|
||||
/// у карточки без разобранного содержимого модуль пуст (Summary = "").
|
||||
/// </remarks>
|
||||
public interface IContentCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Блок «О заявке» (строки с метками, ≤2000).
|
||||
/// </summary>
|
||||
public string Summary { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «файлы» карточки (медиа/документы; объекты — в S3/MinIO).
|
||||
/// </summary>
|
||||
public interface IFileCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Метаданные файлов карточки (пусто — файлов нет).
|
||||
/// </summary>
|
||||
public IReadOnlyList<CardFile> Files { get; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «файл» (карточка создана из загруженного/импортированного файла).
|
||||
/// </summary>
|
||||
public interface IFileSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Ключ объекта в хранилище (MinIO/локальная папка).
|
||||
/// </summary>
|
||||
public string ObjectKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя файла.
|
||||
/// </summary>
|
||||
public string FileName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Размер файла в байтах.
|
||||
/// </summary>
|
||||
public long SizeBytes { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «ссылки» карточки.
|
||||
/// </summary>
|
||||
public interface ILinkCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Прикреплённые ссылки (пусто — ссылок нет).
|
||||
/// </summary>
|
||||
public IReadOnlyList<CardLink> Links { get; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «создано вручную/локально» (карточка без внешнего первоисточника).
|
||||
/// </summary>
|
||||
/// <remarks>Покрывает ручное создание карточки пользователем (в UI «Выбранных» и будущих дашбордов).</remarks>
|
||||
public interface ILocalSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Id автора (оператора тенанта), создавшего карточку; null — неизвестен.
|
||||
/// </summary>
|
||||
public string? AuthorId { get; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «размещение»: контейнер карточки и флаги состояния на доске.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Контейнер (колонка/стадия/зона) — единственное «место» карточки; смена контейнера = переход карточки
|
||||
/// (см. <see cref="ICardMover"/>). «Архив/корзина/отклонено/выполнено» — такие же контейнеры со своими
|
||||
/// политиками, а не отдельные сущности. PrevContainerId — для возврата из архива/корзины.
|
||||
/// </remarks>
|
||||
public interface ILocatedCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Id контейнера, в котором лежит карточка.
|
||||
/// </summary>
|
||||
public string ContainerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Предыдущий контейнер (для возврата); null — возврат неприменим.
|
||||
/// </summary>
|
||||
public string? PrevContainerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Признак «новое» (подсветка на доске; снимается просмотром/переходом).
|
||||
/// </summary>
|
||||
public bool IsNew { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «напоминание»: отложенная карточка (контейнер hold).
|
||||
/// </summary>
|
||||
public interface IRemindableCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Напоминание об «Отложено»; null — напоминания нет.
|
||||
/// </summary>
|
||||
public CardReminder? Reminder { get; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «импорт данных»: карточка создана из строки/колонки таблицы (csv/sheet/БД).
|
||||
/// </summary>
|
||||
public interface IRowSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Id таблицы/импорта (внешний идентификатор источника данных).
|
||||
/// </summary>
|
||||
public string TableId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id строки в таблице (первичный ключ строки-источника).
|
||||
/// </summary>
|
||||
public string RowId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id колонки, из которой взят заголовок/содержимое; null — колонка не применима.
|
||||
/// </summary>
|
||||
public string? ColumnId { get; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник карточки: всё, откуда карточка может прийти.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Полиморфная иерархия вместо enum-свойства «тип источника»: конкретные варианты несут свои поля
|
||||
/// (Telegram — диалог/сообщение/тема, Web — url, AI — провайдер/модель и т.д.), а общий контракт —
|
||||
/// только подпись, ссылку на оригинал и сырое содержимое. Составной источник (<see cref="ICompositeSource"/>)
|
||||
/// описывает цепочку «первоисточник → обработка» (например, сообщение Telegram, разобранное ИИ).
|
||||
/// </remarks>
|
||||
public interface ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Подпись источника в UI: «@freelance», «hh.ru», «файл leads.csv»…
|
||||
/// </summary>
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на оригинал: t.me/…, https://…, objectKey; null — оригинала нет (локальное создание).
|
||||
/// </summary>
|
||||
public string? OriginRef { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Сырое содержимое (текст/JSON), если хранится; null — не хранится.
|
||||
/// </summary>
|
||||
public string? RawPayload { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Время получения/создания исходных данных.
|
||||
/// </summary>
|
||||
public DateTimeOffset ReceivedAt { get; }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «Telegram»: канал/группа/чат/форум, конкретное сообщение.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Покрывает все варианты источника Telegram: канал (DialogId=peer, TopicId=null), группа с темами
|
||||
/// (TopicId задан), личный чат. Ссылка на оригинал строится из PeerHandle/PeerId и MessageId
|
||||
/// (см. контракт tgSourceUrl текущего фронта).
|
||||
/// </remarks>
|
||||
public interface ITelegramSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Id диалога (peer) в Telegram.
|
||||
/// </summary>
|
||||
public string DialogId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id сообщения в диалоге.
|
||||
/// </summary>
|
||||
public long MessageId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Handle канала (без «@»); null — диалог без username (приватный).
|
||||
/// </summary>
|
||||
public string? PeerHandle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя канала/группы/чата для отображения.
|
||||
/// </summary>
|
||||
public string PeerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id темы форума (для групп с темами); null — темы нет.
|
||||
/// </summary>
|
||||
public string? TopicId { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «история движения» карточки (создание и смены контейнера).
|
||||
/// </summary>
|
||||
public interface ITraceableCard
|
||||
{
|
||||
/// <summary>
|
||||
/// История движения, свежие записи — в конце списка (пусто — истории нет).
|
||||
/// </summary>
|
||||
public IReadOnlyList<CardHistoryEntry> History { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Модуль «ТЗ»: текст технического задания/заметки-задания по карточке.
|
||||
/// </summary>
|
||||
public interface ITzCard
|
||||
{
|
||||
/// <summary>
|
||||
/// Текст ТЗ (пусто — ТЗ нет).
|
||||
/// </summary>
|
||||
public string TzText { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Источник «ссылка на сайт/объявление» (карточка создана из веб-страницы).
|
||||
/// </summary>
|
||||
public interface IWebSource : ISource
|
||||
{
|
||||
/// <summary>
|
||||
/// Адрес страницы/объявления.
|
||||
/// </summary>
|
||||
public string Url { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Имя сайта (домен/бренд); null — не определено.
|
||||
/// </summary>
|
||||
public string? SiteName { get; }
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Deal.Modules.Cards.Application;
|
||||
|
||||
/// <summary>
|
||||
/// Контекст перехода карточки: кто инициировал и что делать с обучением.
|
||||
/// </summary>
|
||||
/// <remarks>Переход — единый механизм (<see cref="ICardMover"/>); контекст отличает действие пользователя
|
||||
/// (обучать ML), системы (автоархив/очистка) и ИИ/ML (классификация). Reason — для возвратов из отсева
|
||||
/// и ручных исключений, чтобы ML/ИИ учились на решении.</remarks>
|
||||
public sealed record TransitionContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Инициатор перехода: <c>user</c>|<c>system</c>|<c>ai</c>|<c>ml</c>.
|
||||
/// </summary>
|
||||
public required string Actor { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Причина (возврат из отсева, ручная отмена); null — нет.
|
||||
/// </summary>
|
||||
public string? Reason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Обучать ML на этом действии (действия пользователя — да; системные — нет).
|
||||
/// </summary>
|
||||
public bool Learn { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Deal.Modules.Cards;
|
||||
|
||||
/// <summary>
|
||||
/// Маркер модуля Cards: используется для DI-сканирования и тестов.
|
||||
/// </summary>
|
||||
public sealed class CardsModuleMarker
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Deal.SharedKernel\Deal.SharedKernel.csproj" />
|
||||
<ProjectReference Include="..\Deal.Contracts\Deal.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user