Files
Deal/docs/architecture/2026-09-09-unified-card.md
Rustam Khalimov 27c7831910
ci / build-test (push) Canceled after 0s
Deal — единая кодовая база
SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/
Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue,
контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер),
Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог).

Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0,
тесты 1340/130/52/38/9 зелёные.
2026-09-11 23:56:47 +03:00

93 lines
6.6 KiB
Markdown

# Дейл — единая модель карточки (unified card)
> Исторический документ (дизайн этапа 9, 2026-09-09). Актуальное состояние — `docs/superpowers/STATUS.md` и `docs/technical/Техническая-документация-Дейл.md`.
> Дата: 2026-09-09
> Статус: дизайн согласован с владельцем продукта (в чате), начало реализации.
> Связанные документы: `docs/spec/ТЗ-дейл-новая-архитектура.md`, `docs/architecture/2026-09-05-deal-architecture-design.md`.
## Проблема
Сейчас в системе **два «домена» карточек**, хотя по смыслу это одна сущность:
| | Канбан (`/api/leads`) | «Выбранные» (`/api/projects`) |
|---|---|---|
| Таблица | `Cards` | `ProjectCards` |
| Контейнер | колонка `inbox/board/archive/trash/taken` | стадия `planned…finished/rejected` |
| «Взять в работу» | `col=taken` + **копия полей** в `ProjectCards` | создание второй записи |
| Драйвер/карточка | `CardDto` | `ProjectCardDto` |
Переход «лид → проектная карточка» — это **клонирование в другую сущность**: у карточки меняется id, теряется связность истории, третий вид карточки/дашборда потребует третьей таблицы и третьего конвейера.
**Решение (согласовано):** карточка — **один агрегат** во всех дашбордах. Понятие «лид» упраздняется: сообщение из канала — это *входные данные*, из которых создаётся карточка. «Взял в работу» — это **переход карточки в другой контейнер** той же доски пространства «Выбранные», а не создание новой записи.
## Модель (C#)
### Ядро
```csharp
/// Единственное, что есть у любой карточки.
public interface ICard
{
string Id { get; }
string Title { get; }
ISource Source { get; } // откуда пришла (см. ниже)
}
/// Типизированная проекция для сценариев, которым нужен конкретный источник.
public interface ICard<TSource> : ICard where TSource : ISource
{
new TSource Source { get; }
}
```
### Источники (ISource) — иерархия, а не enum-свойство
- `ISource` — общее: `DisplayName`, `OriginRef`, `RawPayload`, `ReceivedAt`.
- Простые: `ILocalSource`, `IWebSource`, `IFileSource`.
- Сложные: `ITelegramSource` (dialogId/messageId/peer/topic), `IRowSource` (импорт колонки/строки), `IApiSource`, `IAiSource` (провайдер+модель+агент), `ICompositeSource { Origin, Pipeline[] }`.
### Модули-роли карточки (опциональные части одного агрегата)
`IContentCard` (блок «О заявке»), `IBudgetedCard`, `IContactCard`, `IAttributedCard` (стек/грейд/локация — настраиваемые атрибуты тенанта), `ICommentableCard`, `ILinkCard`, `IFileCard`, `ITzCard`, `ITraceableCard` (история), `IRemindableCard`, `ILocatedCard` (контейнер + prev + isNew).
Вид карточки = композиция модулей, **не класс-наследник**. Новый дашборд/вид — новая композиция + при необходимости новый модуль.
### Контейнеры (общая база колонок/стадий/зон)
```csharp
public interface IContainer
{
string Id { get; }
string Name { get; }
string Color { get; }
int Order { get; }
IContainerRules? Rules { get; } // фильтры попадания (пользовательские колонки)
IContainerPolicy Policy { get; } // поведение (роль, не enum)
}
```
Политики: возврат/очистка (корзина 7д, архив 90д), терминальность («Отклонено/Выполнено» — только ручная очистка), «выбранные не попадают в архив дашборда». Отсев пайплайна — **не карточка**, вне этой модели.
### Переходы
Один `ICardMover.MoveAsync(card, toContainerId, ctx)`; правила — в политиках контейнеров и «воротах» между пространствами; побочные эффекты карточка делает через свои модули (`ITraceableCard` пишет историю, `IRemindableCard` сбрасывает напоминание, `ILocatedCard.IsNew=false`).
## Терминология
- ~~лид, lead~~ → **карточка (card)**; входное сообщение → **сообщение-источник**.
- ~~проектная карточка~~ → карточка в контейнерах пространства «Выбранные».
- «Взять в работу» → переход в контейнер `planned`.
## Что меняется
- **БД**: таблицы `Cards` + `ProjectCards` → одна `Cards` (+ модульные данные); доски и стадии — единый реестр контейнеров; удаляется `ProjectCards`, перенос `LeadComments` в модуль карточки.
- **Бэк**: модули Kanban и Projects объединяются в один модуль карточки/контейнеров; порты/сервисы/адаптеры/DTO — единые.
- **Pipeline**: создаёт карточку (не «лид»), кладёт в контейнер по правилам.
- **API**: единый контракт `/api/cards` + `/api/containers`; `/api/leads`, `/api/projects` упраздняются (фронт переписывается).
- **Фронт**: один state-слайс карточек, один рендер карточки/драйвера, один канбан-компонент.
## Границы этапа
Данные тестовые — схема пересоздаётся, миграции данных нет. Вне рамок: Kafka, «третьи» дашборды (архитектура готова), разовые миграции.